package top.cardone.generator.code.template;

import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import freemarker.ext.beans.BeansWrapper;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;
import top.cardone.context.ApplicationContextHolder;
import top.cardone.generator.code.mapper.*;
import top.cardone.template.support.TemplateSupport;

import java.io.File;
import java.util.List;
import java.util.Map;

/**
 * spring + spring mvc + spring jdbc
 *
 * @author yao hai tao
 */
@lombok.Getter
@lombok.Setter
@lombok.experimental.Accessors(chain = true)
@lombok.extern.log4j.Log4j2
public class RunSSSTemplate implements RunTemplate {
    private Map<String, Object> contextMap = Maps.newHashMap();

    private String extendName = "Oracle";

    private Map<String, Object> model = Maps.newHashMap();

    private String outputDir;

    private ProjectMapper projectMapper;

    private List<String> serialVersionUIDList = Lists.newArrayList();

    private Map<String, Object> variables;

    private Map<String, Map<String, String>> makeTable;

    @Override
    public List<PoMapper> findListPoMapper() {
        final List<PoMapper> poMapperList = Lists.newArrayList();

        if (CollectionUtils.isEmpty(this.projectMapper.getModuleMapperList())) {
            return poMapperList;
        }

        for (final ModuleMapper moduleMapper : this.projectMapper.getModuleMapperList()) {
            if (CollectionUtils.isEmpty(moduleMapper.getBusinessMapperList())) {
                continue;
            }

            for (final BusinessMapper businessMapper : moduleMapper.getBusinessMapperList()) {
                if (StringUtils.isEmpty(businessMapper.getTableName())) {
                    continue;
                }

                final EntityMapper entityMapper = new EntityMapper();

                entityMapper.setCode(businessMapper.getCode());
                entityMapper.setTableName(businessMapper.getTableName());

                businessMapper.setEntityMapper(entityMapper);

                poMapperList.add(entityMapper);
            }
        }

        return poMapperList;
    }

    private void initContext() {
        final String packageDir = StringUtils.replace(this.projectMapper.getPackageCode(), ".", File.separator);

        if (MapUtils.isNotEmpty(variables)) {
            this.contextMap.putAll(variables);
        }

        this.contextMap.put("outputDir", this.outputDir);

        this.contextMap.put("packageDir", packageDir);

        this.model.put("statics", BeansWrapper.getDefaultInstance().getStaticModels());

        this.model.put("projectMapper", this.projectMapper);

        this.model.put("packageCode", this.projectMapper.getPackageCode());

        this.model.put("extendName", this.extendName);

        this.model.put("projectCode", this.projectMapper.getCode());

        this.contextMap.put("extendName", this.extendName);

        this.contextMap.put("projectCode", this.projectMapper.getCode());
    }

    private void makeFile(final String templateString, final String templateName, final Boolean overrideFile) throws Exception {
        RunSSSTemplate.log.info(templateString);

        RunSSSTemplate.log.info(templateName);

        final String filePathName = ApplicationContextHolder.func(TemplateSupport.class, templateSupport -> templateSupport.processString(templateString, contextMap));

        RunSSSTemplate.log.info(filePathName);

        final File file = new File(filePathName);

        if (file.exists()) {
            RunSSSTemplate.log.info("file.exists():true");

            if (!overrideFile) {
                return;
            }
        }

        this.modelPutSerialVersionUID();

        final String data = ApplicationContextHolder.func(TemplateSupport.class, templateSupport -> templateSupport.process(templateName, model));

        FileUtils.writeStringToFile(file, data, Charsets.UTF_8);
    }

    private void modelPutSerialVersionUID() {
        String serialVersionUID = RandomStringUtils.randomNumeric(18);

        while (StringUtils.startsWithAny(serialVersionUID, new String[]{"0", "9"}) || this.serialVersionUIDList.contains(serialVersionUID)) {
            serialVersionUID = RandomStringUtils.randomNumeric(18);
        }

        this.serialVersionUIDList.add(serialVersionUID);

        this.model.put("serialVersionUID", serialVersionUID);
    }

    @Override
    public void run() throws Exception {
        if (MapUtils.isEmpty(this.makeTable)) {
            return;
        }

        if (CollectionUtils.isEmpty(this.projectMapper.getModuleMapperList())) {
            return;
        }

        this.initContext();

        for (final ModuleMapper moduleMapper : this.projectMapper.getModuleMapperList()) {
            this.contextMap.put("moduleMapperCode", moduleMapper.getCode());

            this.model.put("moduleMapperCode", moduleMapper.getCode());

            this.model.put("moduleMapper", moduleMapper);

            if (CollectionUtils.isEmpty(moduleMapper.getBusinessMapperList())) {
                continue;
            }

            this.model.put("projectMapper", this.projectMapper);

            for (final BusinessMapper businessMapper : moduleMapper.getBusinessMapperList()) {
                businessMapper.setName(StringUtils.defaultIfBlank(businessMapper.getName(), moduleMapper.getName()));

                this.setBusiness(businessMapper);

                if (businessMapper.getEntityMapper() != null && MapUtils.isNotEmpty(businessMapper.getEntityMapper().getFieldMapperMap())) {
                    this.makeTable("entity");
                }

                this.makeTable("business");
            }

            this.makeTable("module");
        }

        this.makeTable("project");
    }

    private void makeTable(String key) throws Exception {
        if (MapUtils.isEmpty(this.makeTable)) {
            return;
        }

        for (Map.Entry<String, Map<String, String>> makeTableEntry : this.makeTable.entrySet()) {
            if (StringUtils.startsWithIgnoreCase(makeTableEntry.getKey(), key)) {
                boolean overrideFile = StringUtils.endsWithIgnoreCase(makeTableEntry.getKey(), "overrideFile");

                for (Map.Entry<String, String> makeEntry : makeTableEntry.getValue().entrySet()) {
                    this.makeFile(makeEntry.getKey(), makeEntry.getValue(), overrideFile);
                }
            }
        }
    }

    private void setBusiness(final BusinessMapper businessMapper) {
        this.contextMap.put("filename", businessMapper.getCode());

        this.contextMap.put("businessCode", businessMapper.getCode());

        this.model.put("businessCode", businessMapper.getCode());

        this.model.put("entityMapper", businessMapper.getEntityMapper());

        if (businessMapper.getEntityMapper() == null) {
            businessMapper.setName(StringUtils.defaultIfBlank(businessMapper.getName(), businessMapper.getCode()));

            this.model.put("businessName", businessMapper.getName());
        } else {
            businessMapper.setName(StringUtils.defaultIfBlank(businessMapper.getEntityMapper().getRemarks(), businessMapper.getEntityMapper().getCode()));

            this.model.put("businessName", businessMapper.getName());
        }

        this.model.put("businessMapper", businessMapper);
    }
}
